.. _Using NeurEco Python API on a Discrete Dynamic problem: Tutorial: using NeurEco Python API for a Discrete Dynamic problem ======================================================================== The following section uses two test cases: * The test case :std:ref:`Temperature forecasting test case`. * The test case :std:ref:`Nonlinear oscillator test case`. These test cases are included in the NeurEco installation package. Discrete Dynamic proposes various settings for the build and the evaluation. This tutorial is divided into two parts: :std:ref:`Build NeurEco Discrete Dynamic model with the Python API` and :std:ref:`Evaluate NeurEco Discrete Dynamic model with the Python API`. Building a discrete dynamic model =================================== There are two main options to build a **Discrete Dynamic** model: * with a validation percentage, the validation data is chosen from the training data by NeurEco * with validation data, the validation data is set manually. For each option, the build can be done without selecting any of the advanced settings (steady state and hidden state, see :std:ref:`Build NeurEco Discrete Dynamic model with the Python API`), or with these settings provided by the user. Create an empty directory (TemperatureForecasting Example), extract the :std:ref:`Temperature forecasting test case` test case data there. The created directory contains the following files: * x_first_year.npy * x_second_year.npy * y_first_year.npy * y_second_year.npy Simple build without validation data ----------------------------------------- To build a model without any of the advanced settings and without manually setting validation data: * Import the required libraries (NeurEco and numpy): .. code-block:: python from NeurEco import NeurEcoDynamic as Dynamic import numpy as np * Load the data: .. code-block:: python x_train = np.load("x_first_year.npy") t_train = x_train[:, 0:1] x_train = x_train[:, 1:] y_train = np.load("y_first_year.npy") y_train = y_train[:, 1:] x_year_2 = np.load("x_second_year.npy") t_year_2 = x_year_2[:, 0:1] x_year_2 = x_year_2[:, 1:] y_year_2 = np.load("y_second_year.npy") y_year_2 = y_year_2[:, 1:] * Initialize a NeurEco object to handle the **Discrete Dynamic** problem: .. code-block:: python simple_builder_1= Dynamic.DiscreteDynamic() All the methods provided by the **DiscretDynamic** class, can be viewed by calling the *__method__* attributes: .. code-block:: python print(simple_builder_1.__methods__) .. code-block:: text **** NeurEco Dynamic DiscreteDynamic methods: **** - load - save - delete - evaluate - build - get_input_count - get_output_count - load_model_from_checkpoint - get_number_of_networks_from_checkpoint - get_weights - export_fmu - compute_error To understand what each parameter of any method does and how to use it print the doc of the method: .. code-block:: python print(simple_builder_1.export_fmu.__doc__) .. code-block:: text exports a neureco model to FMU (Functional Mock-up Interface) :param fmu_path: string : path where to save the fmu file :return: export_status: int: 0 if export is successful, other int if no * To start the build, run the **build** method with the building parameters adjusted to the problem at hand (see :std:ref:`Build NeurEco Discrete Dynamic model with the Python API`). For this example, the validation percentage is set to 30%. Meaning that NeurEco will use the last 30% of the training trajectory as validation data. For example, if the training trajectory contains 1000 time steps, the first 700 steps will be used for training and the last 300 steps will be used for validation. .. code-block:: python simple_builder_1.build(train_time_list=[t_train], train_exc_list=[x_train], train_out_list=[y_train], valid_percentage=30, # the rest of these parameters are optional write_model_to="./TemperatureForecasting/TemperatureForecasting_simple1.ernn", checkpoint_address="./TemperatureForecasting/TemperatureForecasting_simple1.checkpoint") .. note:: The data (excitations, time and outputs) are always provided as lists. If there are multiple separate experiences (trajectories), the user should avoid stacking such data, and pass a list of arrays instead (each experience is an array). During the build NeurEco saves the intermediate modes to the checkpoint file (defined by the parameter **checkpoint_address**). To load and use the intermediate models from this checkpoint: .. code-block:: python model = Dynamic.DiscreteDynamic() n = model.get_number_of_networks_from_checkpoint("./TemperatureForecasting/TemperatureForecasting_simple1.checkpoint") for i in range(n): model.load_model_from_checkpoint("./TemperatureForecasting/TemperatureForecasting_simple1.checkpoint", i) n_inputs = model.get_input_count() n_outputs = model.get_output_count() print("N° Inputs:", n_inputs) print("N° Outputs:", n_outputs) .. code-block:: text 00h00m00s info > "Checkpoint ./TemperatureForecasting/TemperatureForecasting_simple1.checkpoint" successfully loaded. N° Inputs: 13 N° Outputs: 1 00h00m00s info > "Checkpoint ./TemperatureForecasting/TemperatureForecasting_simple1.checkpoint" successfully loaded. N° Inputs: 13 N° Outputs: 1 ... Simple build with validation data ------------------------------------ In this part, for the same test case, :std:ref:`Temperature forecasting test case`, instead of using 1 year of measurement as training and one year as testing, the second year is used as validation data. * Create the validation data: .. code-block:: python nb_valid_tsteps = x_year_2.shape[0] // 2 t_valid = t_year_2[nb_valid_tsteps:] t_test = t_year_2[:nb_valid_tsteps] x_valid = x_year_2[nb_valid_tsteps:, :] x_test = x_year_2[:nb_valid_tsteps, :] y_valid = y_year_2[nb_valid_tsteps:, :] y_test = y_year_2[:nb_valid_tsteps, :] * Create a new NeurEco object to handle the **Discrete Dynamic** problem: .. code-block:: python simple_builder_2= Dynamic.DiscreteDynamic() * Call the **build** method with the validation data provided explicitly: .. code-block:: python simple_builder_2.build(train_time_list=[t_train], train_exc_list=[x_train], train_out_list=[y_train], valid_time_list=[t_valid], valid_exc_list=[x_valid], valid_out_list=[y_valid], # the rest of these parameters are optional write_model_to="./TemperatureForecasting/TemperatureForecasting_simple2.ernn", checkpoint_address="./TemperatureForecasting/TemperatureForecasting_simple2.checkpoint") During the build NeurEco saves the intermediate modes to the checkpoint file (defined by the parameter **checkpoint_address**). As before, it is possible to load and explore the intermediate models from this checkpoint. .. _Advanced build for Discrete Dynamic Python: Advanced build ---------------------- This part uses :std:ref:`Nonlinear oscillator test case` and illustrates the usage of the advanced parameters (see :std:ref:`Build NeurEco Discrete Dynamic model with the Python API`): * Steady state: (**steady_state_exc** and **steady_state_out**) * Hidden state: (**min_hidden_state**, **max_hidden_state**) Create an empty directory (NonLinearOscillator Example), and extract the :std:ref:`Nonlinear oscillator test case` data there. The data files are inside the directories *"data/learn"*, *"data/valid"* and *"data/test"*. To build the model: * Import the required libraries: NeurEco, os and numpy: .. code-block:: python from NeurEco import NeurEcoDynamic as Dynamic import numpy as np import os * Load the data: .. code-block:: python train_directory = "./data/train" valid_directory = "./data/valid" test_directory = "./data/test" # Training data train_excitations = [] train_times = [] train_outputs = [] train_file_names = os.listdir(train_directory) for file_name in train_file_names: print(">> Loading the data from the file {0} in the Training Directory".format(file_name)) data = np.genfromtxt(os.path.join(train_directory, file_name), skip_header=True, delimiter=",") if "exc" in file_name: train_excitations.append(data[:, 1:]) train_times.append(data[:, 0:1]) elif "out" in file_name: train_outputs.append(data[:, 1:]) # Validation data valid_excitations = [] valid_times = [] valid_outputs = [] valid_file_names = os.listdir(valid_directory) for file_name in valid_file_names: print(">> Loading the data from the file {0} in the Validation Directory".format(file_name)) data = np.genfromtxt(os.path.join(valid_directory, file_name), skip_header=True, delimiter=",") if "exc" in file_name: valid_excitations.append(data[:, 1:]) valid_times.append(data[:, 0:1]) elif "out" in file_name: valid_outputs.append(data[:, 1:]) # Testing Data test_excitations = [] test_times = [] test_outputs = [] test_file_names = os.listdir(test_directory) for file_name in test_file_names: print(">> Loading the data from the file {0} in the Testing Directory".format(file_name)) data = np.genfromtxt(os.path.join(test_directory, file_name), skip_header=True, delimiter=",") if "exc" in file_name: test_excitations.append(data[:, 1:]) test_times.append(data[:, 0:1]) elif "out" in file_name: test_outputs.append(data[:, 1:]) * Initialize a NeurEco object to handle the **Discrete Dynamic** problem: .. code-block:: python builder = Dynamic.DiscreteDynamic() From the equation governing the outputs, one can see that a stationary state (see :std:ref:`Build NeurEco Discrete Dynamic model with the Python API`) is described by the excitation set to :math:`0` and thus the corresponding output to :math:`0`: * Set **steady_state_exc** to :math:`0` * Set **steady_state_out** to :math:`0` .. note:: The **Discrete Dynamic** model supports providing of only one steady state of the model. The governing equation is of the second degree, which could imply that two hidden states (see :std:ref:`Build NeurEco Discrete Dynamic model with the Python API`) are sufficient to describe the system. Here, one more hidden state is added to take the non linearity into account: * Set **max_hidden_states** to :math:`3` * Call the **build** method: .. code-block:: python builder.build(train_time_list=train_times, train_exc_list=train_excitations, train_out_list=train_outputs, valid_time_list=valid_times, valid_exc_list=valid_excitations, valid_out_list=valid_outputs, steady_state_exc=np.array([0]), steady_state_out=np.array([0]), min_hidden_states=1, max_hidden_states=3, checkpoint_address="./NLOscillator/model.checkpoint", write_model_to="./NLOscillator/model.ernn", inputs_normalize_per_feature=True, outputs_normalize_per_feature=True) Once the build ended, the created model can be loaded and explored: .. code-block:: python evaluator = Dynamic.DiscreteDynamic() load_state = evaluator.load("./NLOscillator/model.ernn") if load_state == 0: print("Loading state = Success") else: print("Loading state = Fail") raise RuntimeError("Error loading the NeurEco dynamic model") n_inputs = evaluator.get_input_count() n_outputs = evaluator.get_output_count() n_init_time_steps = evaluator.get_number_of_init_tsteps() steady_exc = evaluator.get_steady_input() steady_out = evaluator.get_steady_output() print("N° Inputs:", n_inputs) print("N° Outputs:", n_outputs) print("N° Initialization time steps:", n_init_time_steps) print("N° hidden state:", evaluator.get_state_count()) print("Steady state excitations:",steady_exc) print("Steady state outputs:", steady_out) .. code-block:: text Loading state = Success N° Inputs: 1 N° Outputs: 1 N° Initialization time steps: 3 N° hidden state: 3 Steady state excitations: [0.] Steady state outputs: [3.46944695e-18] .. _Evaluating a discrete dynamic model in python: Evaluating a discrete dynamic model in python ================================================== There are two options to evaluate a discrete dynamic model: with and without initial conditions (see :std:ref:`Evaluate NeurEco Discrete Dynamic model with the Python API`). Evaluate a model without initial conditions ------------------------------------------------- * **load** an already created model, here the one created in the previous section for the test case :std:ref:`Temperature forecasting test case`. * Call the **evaluate** method: .. code-block:: python evaluator = Dynamic.DiscreteDynamic() load_state = evaluator.load("./TemperatureForecasting/TemperatureForecasting_simple1.ernn") if load_state == 0: print("Loading state = Success") else: raise RuntimeError("Error loading the neureco model") neureco_outputs_train = evaluator.evaluate([t_train], [x_train]) neureco_outputs_test = evaluator.evaluate([t_year_2], [x_year_2]) * To plot the prediction and the measured data for all the sets in a continuous way (matplotlib library is required): .. code-block:: python import matplotlib.pyplot as plt nb_training_tsteps = int(x_train.shape[0] * 70 / 100) plt.figure(1) plt.plot(np.vstack((t_train, t_year_2)), np.vstack((y_train, y_year_2)), label="measured data", marker='.', markersize=3, linestyle="none") plt.plot(t_train[:nb_training_tsteps], neureco_outputs_train[0][:nb_training_tsteps, :], label="training prediction") plt.plot(t_train[nb_training_tsteps:], neureco_outputs_train[0][nb_training_tsteps:, :], label="validation prediction") plt.plot(t_year_2, neureco_outputs_test[0], label="test prediction") plt.legend() plt.title("Simple Build 1: Automatic Validation Data Selection") plt.show() .. figure:: ../../../images/TempForecastPythonEvalWithoutInit.png :width: 800 :alt: TempForecastPythonEvalWithoutInit :align: center python API operations: evaluating a model without initial condition: test case - Temperature forecasting * To perform a sensitivity analysis using the built model without initialization data: .. code-block:: python sensitivity_array = evaluator.sensitivity(time=t_test, excitations=x_test, id_output=0) x_ticks = ["input " + str(i) for i in range(sensitivity_array.size)] plt.figure(2) plt.bar(x_ticks, sensitivity_array[0, :], color="darkorange") plt.grid() plt.ylabel("Sensitivity") plt.title("Simple build 1 -- Sensitivity analysis") plt.show() .. figure:: ../../../images/TempForecastPythonSensitivityWithoutInit.png :width: 800 :alt: TempForecastPythonSensitivityWithoutInit :align: center python API operations: Performing a sensitivity analysis without initial condition: test case - Temperature forecasting Evaluate a model with the explicit initialization ------------------------------------------------------- * **load** an already created model, here the one created in the previous section for the test case :std:ref:`Temperature forecasting test case`: .. code-block:: python evaluator = Dynamic.DiscreteDynamic() load_state = evaluator.load("./TemperatureForecasting/TemperatureForecasting_simple1.ernn") if load_state == 0: print("Loading state = Success") else: raise RuntimeError("Error loading the neureco model") * Get the number of initialization steps deduced during the build: .. code-block:: python n_init_steps = evaluator.get_number_of_init_tsteps() * Use this number to create initialization arrays to evaluate the training set and the testing set: .. code-block:: python t_train_init = t_train[:n_init_steps, :] x_train_init = x_train[:n_init_steps, :] y_train_init = y_train[:n_init_steps, :] t_year_2_init = t_year_2[:n_init_steps, :] x_year_2_init = x_year_2[:n_init_steps, :] y_year_2_init = y_year_2[:n_init_steps, :] * Evaluate the model using these initialization arrays: .. code-block:: python neureco_outputs_train2 = evaluator.evaluate([t_train], [x_train], initialization_excitations_arrays_list=[x_train_init], initialization_outputs_arrays_list=[y_train_init], initialization_time_arrays_list=[t_train_init])[0] neureco_outputs_test2 = evaluator.evaluate([t_year_2], [x_year_2], initialization_excitations_arrays_list=[x_year_2_init], initialization_outputs_arrays_list=[y_year_2_init], initialization_time_arrays_list=[t_year_2_init])[0] * Plot the prediction and the measured data for all the sets in a continuous way (matplotlib library is required): .. code-block:: python import matplotlib.pyplot as plt neureco_outputs_train2 = evaluator.evaluate([t_train], [x_train], initialization_excitations_arrays_list=[x_train_init], initialization_outputs_arrays_list=[y_train_init], initialization_time_arrays_list=[t_train_init]) neureco_outputs_test2 = evaluator.evaluate([t_year_2], [x_year_2], initialization_excitations_arrays_list=[x_year_2_init], initialization_outputs_arrays_list=[y_year_2_init], initialization_time_arrays_list=[t_year_2_init]) nb_training_tsteps = int(x_train.shape[0] * 70 / 100) plt.figure(1) plt.plot(np.vstack((t_train, t_year_2)), np.vstack((y_train, y_year_2)), label="measured data", marker='.', markersize=3, linestyle="none") plt.plot(t_train[:nb_training_tsteps], neureco_outputs_train2[0][:nb_training_tsteps, :], label="training prediction") plt.plot(t_train[nb_training_tsteps:], neureco_outputs_train2[0][nb_training_tsteps:, :], label="validation prediction") plt.plot(t_year_2, neureco_outputs_test2[0], label="test prediction") plt.legend() plt.title("Simple Build 1: Automatic Validation Data Selection -- Evaluation with initialization") plt.show() .. figure:: ../../../images/TempForecastPythonEvalWithInit.png :width: 800 :alt: TempForecastPythonEvalWithInit :align: center python API operations: evaluating a model with initial condition: test case - Temperature forecasting * Perform a sensitivity analysis using the built model with initialization data: .. code-block:: python n_init = evaluator.get_number_of_init_tsteps() exc_init = x_test[:n_init, :] time_init = t_test[:n_init] out_init = y_test[:n_init, :] time_ = t_test[n_init-1:] excs_ = x_test[n_init-1:, :] sensitivity_array = evaluator.sensitivity(time=time_, excitations=excs_, id_output=0, initialization_excitations=exc_init, initialization_time=time_init, initialization_outputs=out_init) x_ticks = ["input " + str(i) for i in range(sensitivity_array.size)] plt.figure(2) plt.bar(x_ticks, sensitivity_array[0, :], color="darkorange") plt.grid() plt.ylabel("Sensitivity") plt.title("Simple build 2 -- Sensitivity analysis") plt.show() .. figure:: ../../../images/TempForecastPythonSensitivityWithInit.png :width: 800 :alt: TempForecastPythonSensitivityWithInit :align: center python API operations: Performing a sensitivity analysis with initial condition: test case - Temperature forecasting Exporting a Discrete Dynamic model ------------------------------------ To export the model as an FMU file: .. code-block:: python evaluator.export_fmu("./TemperatureForecasting/TemperatureForecasting_simple1.fmu") This export requires *embed* license. .. warning:: Once the NeurEco object is no longer needed, free the memory by deleting the object by calling the **delete** method. For the example above, five objects must be deleted: .. code-block:: python builder.delete() model.delete() simple_builder1.delete() simple_builder2.delete() evaluator.delete()